/*
Copyright (c) 2010, Daniele Simonetti
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "playlistentry.h"
#include "utility.h"

#include <QMessageBox>
#include <QTextStream>
#include <QFileInfo>
#include <QDebug>

#include "../deps/taglib/include/tag.h"
#include "../deps/taglib/include/fileref.h"

PlaylistEntry::PlaylistEntry(QUrl path, bool initLater)
{
    init();

    _path = path;
    getTags();

    if ( initLater )
        return;

    createStream();
}

PlaylistEntry::PlaylistEntry(QString path, bool initLater)
{
    init();

    _path = _path.fromLocalFile(path);
    getTags();

    if ( initLater )
        return;

    createStream();
}

PlaylistEntry::PlaylistEntry()
{
    init();
}

PlaylistEntry::PlaylistEntry(const PlaylistEntry& source)
{
    _path = source._path;
    _lastError = source._lastError;
    _status = source.status();

    getTags();
    createStream();
}

PlaylistEntry::~PlaylistEntry()
{
    if ( _hStream )
        BASS_StreamFree(_hStream);
}

void PlaylistEntry::init()
{
    _cachedLen  = -1;
    _hStream    = 0;
    _lastError  = 0;
    _status     = Stopped;
    _readonly   = true;

    _cachedtrack = 0;
    _cachedyear = 0;
    _cachedbitrate = 0;
    _cachedsamplerate = 0;
    _cachedchannels = 0;
}

bool PlaylistEntry::createStream()
{
    if ( !isNetwork() )
        _hStream = BASS_StreamCreateFile(FALSE,_path.toLocalFile().utf16(),0,0,BASS_UNICODE);
    else
    {
        QByteArray qsUrl = _path.toString().toAscii();
        _hStream = BASS_StreamCreateURL(qsUrl.constData(), 0, BASS_STREAM_AUTOFREE, NULL, this);
    }

    if ( !_hStream )
        _lastError = BASS_ErrorGetCode();

    return _hStream != 0;
}

bool PlaylistEntry::isNetwork() const
{
    return _path.scheme() == "ftp" || _path.scheme() == "http" || _path.scheme() == "https";
}

PlaylistEntry::TrackStatus PlaylistEntry::status() const
{
    return _status;
}

QString PlaylistEntry::fullPath() const
{
    if ( isNetwork() )
        return _path.toString();
    else
        return _path.toLocalFile();
}

QString PlaylistEntry::fileName() const
{
    if ( isNetwork() )
        return "";
    else
        return QFileInfo(_path.toLocalFile()).fileName();
}

QUrl PlaylistEntry::url() const
{
    return _path;
}

QString PlaylistEntry::caption() const
{
    return _cachedcaption;
}

QString PlaylistEntry::title() const
{
    return _cachedtitle;
}

QString PlaylistEntry::artist() const
{
    return _cachedartist;
}

QString PlaylistEntry::genre() const
{
    return _cachedgenre;
}

QString PlaylistEntry::album() const
{
    return _cachedalbum;
}

int PlaylistEntry::track() const
{
    return _cachedtrack;
}

int PlaylistEntry::year() const
{
    return _cachedyear;
}

int PlaylistEntry::bitrate() const
{
    return _cachedbitrate;
}

int PlaylistEntry::channels() const
{
    return _cachedchannels;
}

int PlaylistEntry::samplerate() const
{
    return _cachedsamplerate;
}

int PlaylistEntry::filesize() const
{
    return _cachedsize;
}

bool PlaylistEntry::isValid()
{
    if ( !_hStream && !_lastError )
    {
        bool ret = createStream();
        if ( ret )
            BASS_StreamFree(_hStream);
        _hStream = 0;
        return ret;
    }

    return _hStream != 0;
}

bool PlaylistEntry::play(bool restart)
{
    if ( !_hStream )
    {
        if ( _lastError )
            return false;
        createStream();
    }

    restart |= (_status == Stopped);
    BASS_ChannelSetSync(_hStream, BASS_SYNC_END|BASS_SYNC_ONETIME, 0, &MySyncProc, this);
    if (BASS_ChannelPlay(_hStream,restart) != 0)
        _status = Playing;
    else
        _status = Stopped;
    return _status == Playing;
}

bool PlaylistEntry::pause()
{
    if ( BASS_ChannelStop(_hStream) )
        _status = Paused;
    return _status == Paused;
}

bool PlaylistEntry::stop()
{
    _status = pause() ? Stopped : _status;

    BASS_StreamFree(_hStream);
    _hStream = 0;

    return _status == Stopped;
}

QString PlaylistEntry::toString() const
{
    if ( _cachedcaption.isNull() || _cachedcaption.isEmpty() )
    {
        return isNetwork() ? _path.toString() : _path.toLocalFile();
    }

    return _cachedcaption;
}

void PlaylistEntry::emitEndReached()
{
    emit endReached();
}

bool PlaylistEntry::updateTags()
{
    return getTags();
}

double PlaylistEntry::len() const
{
    if ( _cachedLen < 0 )
    {
        QWORD len = BASS_ChannelGetLength(_hStream, BASS_POS_BYTE); // the length in bytes
        _cachedLen = (int)BASS_ChannelBytes2Seconds(_hStream, len); // the length in seconds
    }

    return _cachedLen;
}

double PlaylistEntry::pos() const
{
    QWORD len = BASS_ChannelGetPosition(_hStream, BASS_POS_BYTE); // the length in bytes
    return BASS_ChannelBytes2Seconds(_hStream, len); // the length in seconds
}

bool PlaylistEntry::setPos(double value)
{
    QWORD pos = BASS_ChannelSeconds2Bytes(_hStream, value);
    return BASS_ChannelSetPosition(_hStream, pos, BASS_POS_BYTE) != 0;
}

bool PlaylistEntry::getTags()
{
    if ( isNetwork() )
    {
        const char* pSTR = (const char*)0xDEADC0DE;
        const char* tICY = BASS_ChannelGetTags(_hStream, BASS_TAG_ICY);
        if ( tICY )
        {
            pSTR = tICY;
            while ( pSTR[0] )
            {                
                QString data(pSTR);

                if ( data.startsWith("icy-name") )
                    _cachedcaption = data.section(":", -1).trimmed();
                else if ( data.startsWith("icy-genre"))
                    _cachedgenre = data.section(":", -1).trimmed();
                else if ( data.startsWith("icy-br"))
                {
                    QString br = data.section(":", -1).trimmed();
                    _cachedbitrate = br.toInt();
                }

                pSTR += strlen(pSTR)+1;
            }
        }
        const char* tMETA = BASS_ChannelGetTags(_hStream, BASS_TAG_META);
        if ( tMETA )
        {
            pSTR = tMETA;
            while ( pSTR[0] )
            {
                QString data(pSTR);
                if (data.startsWith("StreamTitle"))
                {
                    int a = data.indexOf(QChar('\''), 0);
                    int b = data.indexOf(QChar('\''), a+1);
                    _cachedtitle = data.mid(a+1, b-a-1);
                }

                pSTR += strlen(pSTR)+1;
            }
        }
    }
    else
    {
        TagLib::FileName fname(_path.toLocalFile().utf16());
        TagLib::FileRef file(fname);
        TagLib::Tag* tag = NULL;
        TagLib::AudioProperties* audio = NULL;
        if ( file.isNull() )
            return false;
        tag = file.tag();
        audio = file.audioProperties();
        if ( file.file() )
            _readonly = file.file()->readOnly();

        if ( tag != NULL && !tag->isEmpty() )
        {
            util::tagstring_to_qstring(&tag->artist(), tr("<Unknown Artist>"), &_cachedartist);
            util::tagstring_to_qstring(&tag->title(), tr("<Unknown Title>"), &_cachedtitle);
            util::tagstring_to_qstring(&tag->album(), tr("<No Album>"), &_cachedalbum);
            util::tagstring_to_qstring(&tag->genre(), tr("<Unknown Genre>"), &_cachedgenre);
            _cachedcaption = QString("%1 - %2").arg(_cachedartist).arg(_cachedtitle);
            _cachedtrack = tag->track();
            _cachedyear = tag->year();
        }
        if ( audio != NULL )
        {
            _cachedbitrate = audio->bitrate();
            _cachedchannels = audio->channels();
            _cachedsamplerate = audio->sampleRate();
            _cachedLen = audio->length();
        }

        if ( file.file() != NULL )
        {
            _cachedsize = file.file()->length();
        }

        return true;
    }
    return false;
}

void PlaylistEntry::setCaption(QString caption)
{
    _cachedcaption = caption;
}

bool PlaylistEntry::readonly() const
{
    return _readonly;
}



void CALLBACK MySyncProc(HSYNC /*handle*/, DWORD /*channel*/, DWORD /*data*/, void *user)
{
    PlaylistEntry* me = (PlaylistEntry*)user;
    if ( me )
    {
        // Stop
        me->emitEndReached();
    }
}


